/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package stm_database;



import java.io.File;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import java.util.*;


/**
 *
 * @author stepj5am
 */

public class Database {
    
    protected ArrayList<DatabaseItem> items;
    protected HashMap<String, Integer> headers;
    protected File source_file;
    protected int sorted_by;
    protected boolean sort_asc = true;
    protected int top_id;
    
    
    public Database()
    {
        items = new ArrayList<DatabaseItem>();
        headers = new HashMap<String, Integer>();
        source_file = null;
        sorted_by = -1;
        top_id = 0;
    }
    /**
     * vytvori novou prazdnou databazi se sloupci head
     * @param head sloupce nove databaze
     */
    public Database(String[] head)
    {
        items = new ArrayList<DatabaseItem>();
        headers = new HashMap<String, Integer>();
        source_file = null;
        sorted_by = -1;
        top_id = 0;
        headers.put("id", 0);
        for(int i = 0;i < head.length;++i)
        {
            headers.put(head[i], i+1);
        }
    }
    /**
     * nacte databazi ze souboru name
     * @param name: name of file with database items
     */
    public Database(String name) throws IOException
    { 
        sorted_by = -1;
        top_id = 0;
        BufferedReader input;
        input = new BufferedReader(new FileReader(name));
        source_file = new File(name);
        if(input == null)
            throw new BadCommandParameterException("Soubor nenalezen");
        //nacist prvni radek - jmena sloupcu a dat je do headers
        String line = input.readLine() ;
        if(line == null)
            throw new BadFileFormatException("chyba - prazdny soubor");
        String[] words = line.split(";");
        headers = new HashMap<String, Integer>();
        for(int i = 0 ;i < words.length; ++i)
        {
            headers.put(words[i], i);
        }
        items = new ArrayList<DatabaseItem>();
        while((line = input.readLine()) != null)
        {
            //precist cely soubor a dat ho do items
            words = line.split(";");
            if(words.length != headers.size())
                continue;//TODO mozna error pri vstupu - badfileformatexception
            int id = 0;
            try
            {
                id = Integer.parseInt(words[0]);            
            }catch(NumberFormatException e)
            {
                continue;
            }
            items.add(new DatabaseItem(Arrays.copyOfRange(words,1,words.length),id));
            ++top_id;
        }
        input.close();
        
        
    }
    /**
     * zmeni polozku s id , atributy nastavi na new_items
     * @param id
     * @param new_items
     * @throws IOException 
     */
    public void edit_item(String id, String[] new_items) throws IOException            
    {
        
        if(new_items.length >= headers.size())
            throw new IOException("nespravny tvar polozky");
        DatabaseItem[] temp = new DatabaseItem[items.size()];
        items.toArray(temp);
        Integer[] id_to_edit = find_indexes(0, id, temp);
        //mazu polozku ne index
        if(id_to_edit == null)
            throw new IOException("polozka se zadanym indexem neexistuje");
        
        DatabaseItem new_item = items.get(id_to_edit[0]);
        for(int i = 1 ;i <= new_items.length; ++i)
        {
            if(new_items[i-1].isEmpty())
                continue;
            new_item.items.set(i, new_items[i-1]);
            items.set(id_to_edit[0], new_item);
        }
        sorted_by = -1;
        sort_asc = true;
    }
    /*
     * setridi databazi podle klice key. jestli uz je setrizena vzestupne, setridi znovu sestupne a obracene.
     */
    public void sort(String key)throws BadCommandParameterException
    {
        if(!headers.containsKey(key))
            throw new BadCommandParameterException("neplatny index");
        int sort_index =headers.get(key);
        boolean ascending = true;        
        if(sort_index == sorted_by)
        {
            ascending = !sort_asc;
            sort_asc = !sort_asc;
        }
        Collections.sort(items, new DatabaseItemComparator(sort_index, ascending));
        sorted_by = sort_index;
    }/**
     * najde hodnotu podle masky values
     * @param values maska atributu pro hledani polozky
     * @return
     * @throws BadCommandParameterException 
     */
    public DatabaseItem[] find( String[] values) throws BadCommandParameterException
    {
        
        DatabaseItem[] result = new DatabaseItem[items.size()];
        items.toArray(result);
        for(int i = 0 ;i < headers.size();++i)
        {
            if(!values[i].isEmpty())
            {
                Integer[] indexes = find_indexes(i, values[i],result);
                if(indexes == null)
                    return null;
                ArrayList<DatabaseItem> dataitems = new ArrayList<DatabaseItem>();
                for(int x : indexes)
                {
                    dataitems.add(result[x]);
                }
                result = new DatabaseItem[dataitems.size()];
                dataitems.toArray(result);
            }
        }
        
        return result;
    }
    /**
     * ulozi databazi bud do stejneho souboru (path = null) nebo do noveho se jmenem path
     * @param path
     * @throws IOException 
     */
    public void save(String path) throws IOException
    {
        File save = null;
        if(path == null)
             save = source_file;
        else
            save = new File(path);
        if(save == null)
            throw new BadCommandParameterException("nespecifikovany nazev souboru pro ulozeni");
        
        BufferedWriter out = new BufferedWriter(new FileWriter(save));
        Set<Map.Entry<String, Integer>> head = headers.entrySet();
        String[] header = new String[head.size()];
        for(Map.Entry<String, Integer> m : head)
        {
            header[m.getValue()] = m.getKey();
        }
        for(int i = 0 ;i < header.length; ++i)
        {
            if(i != 0)
                out.write(DatabaseItem.delimiter);
            out.write(header[i]);
        }
        out.newLine();
        for(DatabaseItem d : items)
        {
            out.write(d.toString());
            out.newLine();
        }
        out.close();
        
    }
    /**
     * smaze polozku s id 
     * @param id
     * @return
     * @throws BadCommandParameterException 
     */
    public DatabaseItem delete_item(String id) throws BadCommandParameterException
    {  
        DatabaseItem[] i = new DatabaseItem[items.size()];
        items.toArray(i);
        try
        {
            Integer.parseInt(id);
        }catch(NumberFormatException e)
        {
            throw new BadCommandParameterException("zadany index neni cislo");
        }
        Integer[] id_to_delete = find_indexes(0, id, i);
        if(id_to_delete == null)
            throw new BadCommandParameterException("zadany index neexistuje");
        //mazu polozku ne index
        return items.remove(id_to_delete[0].intValue());
    }
    /**vypise polozky od id from do id to
     * 
     */
    public String print(int from, int to)            
    {
        StringBuilder sb = new StringBuilder();
        Set<Map.Entry<String, Integer>> head = headers.entrySet();
        String[] header = new String[head.size()];
        for(Map.Entry<String, Integer> m : head)
        {
            header[m.getValue()] = m.getKey();
        }
        for(int i = 0 ;i < header.length; ++i)
        {
            if(i != 0)
                sb.append(DatabaseItem.delimiter);
            sb.append(header[i]);
        }
        
        for(int i = from; i<to ;++i)
        {
            if(i >= items.size())
                break;
            
            sb.append("\n");
            sb.append(items.get(i).toString());
            
        }
        return sb.toString();
    }
    /**
     * prida polozku
     * @param to_add
     * @throws BadCommandParameterException 
     */
    public void add_item(String[] to_add) throws BadCommandParameterException
    {
        if(to_add.length != (headers.size()-1))
            throw new BadCommandParameterException("spatny format pridavane polozky");
        DatabaseItem item_to_add = new DatabaseItem(to_add, top_id++);
        items.add(item_to_add);
        sorted_by = -1;
        sort_asc = true;
    }
    /**
     * pomocna metoda na nalezeni indexu v items
     * @param key
     * @param value
     * @param source
     * @return
     * @throws BadCommandParameterException 
     */
    protected Integer[] find_indexes(int key, String value, DatabaseItem[] source)throws BadCommandParameterException
    {
        int index = key;
        ArrayList<Integer> result = new ArrayList<Integer>();
        if(index < 0 || index >= headers.size())
        {
            throw new BadCommandParameterException("neplatny index");
        }
        //kdyz neni setrizeny tak projit a najit vsechno 
        if(index != sorted_by)
        {
            int limit = source.length;
            for(int i = 0 ;i < limit;++i)
            {
                //if(items.get(i).get(index).equals(value))
                if(source[i].get(index).equals(value))
                    result.add(i);
            }
        }else
        {
            //kdyz je setrizeny tak projit binarne a najit prvni + vsechny dalsi
            int low = 0;
            int high = source.length;
            int first_found = -1;
            while(low != high)
            {
                int medium = (high -low)/2;
                //id se porovnava podle int ne string
                int compare = 0;
                if(index !=0)
                {
                    compare = source[low + medium].get(index).compareTo(value);
                }else
                {
                    compare =  -1 *new Integer(Integer.parseInt(source[low + medium].get(index))).compareTo
                            (new Integer(Integer.parseInt(value)));
                }
                if(compare == 0)
                {
                    //potrebuju najit prvni z nich
                    if((low + medium) == 0 || (!source[low + medium - 1].get(index).equals(value)))
                    {
                        //nasel jsem prvni index
                        first_found = low + medium;
                        break;
                    }else
                    {
                        //musim najit prvni vyskyt value
                        high = low + medium;
                    }
                    
                }
                if(compare < 0)
                {
                    high = low + medium;
                }
                if(compare > 0)
                {
                    low = low + medium;
                }
            }
            if(first_found == -1)
                return null;//prvek neexistuje
            else
            {
                int i = first_found;
                while(source[i].get(index).equals(value))
                {
                    result.add(i);
                    ++i;
                }
            }
        }
        if(result.isEmpty())
            return null;
        Integer[] result1 = new Integer[result.size()];
        result.toArray(result1);
        return result1;
    }
    /**
     * pocet polozek v databazi
     * @return 
     */
    public int size()
    {
        return items.size();
    }
    /**
     * pocet slupcu v databazi
     * @return 
     */
    public int column_number()
    {
        return headers.size();
    }
    
    
            
            

}
